/*
 * WPA Supplicant / UNIX domain socket -based control interface
 * Copyright (c) 2004, Jouni Malinen <jkmaline@cc.hut.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include "common.h"
#include "eloop.h"
#include "wpa.h"
#include "wpa_supplicant.h"
#include "config.h"
#include "eapol_sm.h"
#include "wpa_supplicant_i.h"
#include "ctrl_iface.h"
#include "l2_packet.h"


static const char * wpa_cipher_txt(int cipher)
{
	switch (cipher) {
	case WPA_CIPHER_NONE:
		return "NONE";
	case WPA_CIPHER_WEP40:
		return "WEP-40";
	case WPA_CIPHER_WEP104:
		return "WEP-104";
	case WPA_CIPHER_TKIP:
		return "TKIP";
	case WPA_CIPHER_CCMP:
		return "CCMP";
	default:
		return "UNKNOWN";
	}
}


static const char * wpa_key_mgmt_txt(int key_mgmt)
{
	switch (key_mgmt) {
	case WPA_KEY_MGMT_IEEE8021X:
		return "WPA/IEEE 802.1X/EAP";
	case WPA_KEY_MGMT_PSK:
		return "WPA-PSK";
	case WPA_KEY_MGMT_NONE:
		return "NONE";
	case WPA_KEY_MGMT_IEEE8021X_NO_WPA:
		return "IEEE 802.1X (no WPA)";
	default:
		return "UNKNOWN";
	}
}


static const char * wpa_state_txt(int state)
{
	switch (state) {
	case WPA_DISCONNECTED:
		return "DISCONNECTED";
	case WPA_SCANNING:
		return "SCANNING";
	case WPA_ASSOCIATING:
		return "ASSOCIATING";
	case WPA_ASSOCIATED:
		return "ASSOCIATED";
	case WPA_4WAY_HANDSHAKE:
		return "4WAY_HANDSHAKE";
	case WPA_GROUP_HANDSHAKE:
		return "GROUP_HANDSHAKE";
	case WPA_COMPLETED:
		return "COMPLETED";
	default:
		return "UNKNOWN";
	}
}


static void wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s,
					  int sock, char *cmd,
					  struct sockaddr_un *from,
					  socklen_t fromlen)
{
	char *value;

	value = strchr(cmd, ' ');
	if (value == NULL)
		goto fail;
	*value++ = '\0';

	wpa_printf(MSG_DEBUG, "CTRL_IFACE SET '%s'='%s'", cmd, value);
	if (strcasecmp(cmd, "EAPOL::heldPeriod") == 0) {
		eapol_sm_configure(wpa_s->eapol,
				   atoi(value), -1, -1, -1);
	} else if (strcasecmp(cmd, "EAPOL::authPeriod") == 0) {
		eapol_sm_configure(wpa_s->eapol,
				   -1, atoi(value), -1, -1);
	} else if (strcasecmp(cmd, "EAPOL::startPeriod") == 0) {
		eapol_sm_configure(wpa_s->eapol,
				   -1, -1, atoi(value), -1);
	} else if (strcasecmp(cmd, "EAPOL::maxStart") == 0) {
		eapol_sm_configure(wpa_s->eapol,
				   -1, -1, -1, atoi(value));
	} else
		goto fail;
	sendto(sock, "OK\n", 3, 0,
	       (struct sockaddr *) from, fromlen);
	return;

fail:
	sendto(sock, "FAIL\n", 5, 0,
	       (struct sockaddr *) from, fromlen);
}


static void wpa_supplicant_ctrl_iface_preauth(struct wpa_supplicant *wpa_s,
					      int sock, char *addr,
					      struct sockaddr_un *from,
					      socklen_t fromlen)
{
	u8 bssid[ETH_ALEN];

	if (hwaddr_aton(addr, bssid)) {
		wpa_printf(MSG_DEBUG, "CTRL_IFACE PREAUTH: invalid address "
			   "'%s'", addr);
		goto fail;
	}

	wpa_printf(MSG_DEBUG, "CTRL_IFACE PREAUTH " MACSTR, MAC2STR(bssid));
	rsn_preauth_deinit(wpa_s);
	if (rsn_preauth_init(wpa_s, bssid))
		goto fail;

	sendto(sock, "OK\n", 3, 0, (struct sockaddr *) from, fromlen);
	return;

fail:
	sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) from, fromlen);
}


void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,
				       void *sock_ctx)
{
	struct wpa_supplicant *wpa_s = eloop_ctx;
	u8 buf[256];
	int res, res1;
	struct sockaddr_un from;
	socklen_t fromlen = sizeof(from);
	char *reply, *pos, *end;

	res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
		       (struct sockaddr *) &from, &fromlen);
	if (res < 0) {
		perror("recvfrom(ctrl_iface)");
		return;
	}
	buf[res] = '\0';
	wpa_hexdump_ascii(MSG_DEBUG, "RX ctrl_iface", buf, res);

	if (strcmp(buf, "MIB") == 0) {
		reply = malloc(2048);
		if (reply == NULL) {
			sendto(sock, "FAIL\n", 5, 0,
			       (struct sockaddr *) &from, fromlen);
			return;
		}
		res1 = wpa_get_mib(wpa_s, reply, 2048);
		if (res1 < 0) {
			free(reply);
			return;
		}
		res = eapol_sm_get_mib(wpa_s->eapol, reply + res1,
				       2048 - res1);
		if (res < 0) {
			free(reply);
			return;
		}
		sendto(sock, reply, res1 + res, 0,
		       (struct sockaddr *) &from, fromlen);
		free(reply);
	} else if (strcmp(buf, "STATUS") == 0) {
		reply = malloc(2048);
		if (reply == NULL) {
			sendto(sock, "FAIL\n", 5, 0,
			       (struct sockaddr *) &from, fromlen);
			return;
		}
		pos = reply;
		end = reply + 2048;
		pos += snprintf(pos, end - pos, "bssid=" MACSTR "\n",
				MAC2STR(wpa_s->bssid));
		if (wpa_s->current_ssid) {
			pos += snprintf(
				pos, end - pos, "ssid=%s\n",
				wpa_ssid_txt(wpa_s->current_ssid->ssid,
					     wpa_s->current_ssid->ssid_len));
		}
		pos += snprintf(pos, end - pos,
				"pairwise_cipher=%s\n"
				"group_cipher=%s\n"
				"key_mgmt=%s\n"
				"wpa_state=%s\n",
				wpa_cipher_txt(wpa_s->pairwise_cipher),
				wpa_cipher_txt(wpa_s->group_cipher),
				wpa_key_mgmt_txt(wpa_s->key_mgmt),
				wpa_state_txt(wpa_s->wpa_state));

		res = eapol_sm_get_status(wpa_s->eapol, pos, end - pos);
		if (res < 0) {
			free(reply);
			return;
		}
		pos += res;

		if (wpa_s->preauth_eapol) {
			pos += snprintf(pos, end - pos, "Pre-authentication "
					"EAPOL state machines:\n");
			res = eapol_sm_get_status(wpa_s->preauth_eapol,
						  pos, end - pos);
			if (res < 0) {
				free(reply);
				return;
			}
			pos += res;
		}

		sendto(sock, reply, pos - reply, 0,
		       (struct sockaddr *) &from, fromlen);
		free(reply);
	} else if (strcmp(buf, "PMKSA") == 0) {
		reply = malloc(2048);
		if (reply == NULL) {
			sendto(sock, "FAIL\n", 5, 0,
			       (struct sockaddr *) &from, fromlen);
			return;
		}
		res = pmksa_cache_list(wpa_s, reply, 2048);
		if (res < 0) {
			free(reply);
			return;
		}
		sendto(sock, reply, res, 0,
		       (struct sockaddr *) &from, fromlen);
		free(reply);
	} else if (strncmp(buf, "SET ", 4) == 0) {
		wpa_supplicant_ctrl_iface_set(wpa_s, sock, buf + 4,
					      &from, fromlen);
	} else if (strcmp(buf, "LOGON") == 0) {
		eapol_sm_notify_logoff(wpa_s->eapol, FALSE);
		sendto(sock, "OK\n", 3, 0, (struct sockaddr *) &from, fromlen);
	} else if (strcmp(buf, "LOGOFF") == 0) {
		eapol_sm_notify_logoff(wpa_s->eapol, TRUE);
		sendto(sock, "OK\n", 3, 0, (struct sockaddr *) &from, fromlen);
	} else if (strcmp(buf, "REASSOCIATE") == 0) {
		wpa_s->reassociate = 1;
		wpa_supplicant_req_scan(wpa_s, 0, 0);
		sendto(sock, "OK\n", 3, 0, (struct sockaddr *) &from, fromlen);
	} else if (strncmp(buf, "PREAUTH ", 8) == 0) {
		wpa_supplicant_ctrl_iface_preauth(wpa_s, sock, buf + 8,
						  &from, fromlen);
	} else {
		sendto(sock, "UNKNOWN COMMAND\n", 16, 0,
		       (struct sockaddr *) &from, fromlen);
	}
}


int wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
{
	struct sockaddr_un addr;
	int s;

	wpa_s->ctrl_sock = -1;

	if (wpa_s->conf->ctrl_interface == NULL)
		return 0;

	if (strlen(wpa_s->conf->ctrl_interface) >= sizeof(addr.sun_path))
		return -1;

	s = socket(PF_UNIX, SOCK_DGRAM, 0);
	if (s < 0) {
		perror("socket(PF_UNIX)");
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, wpa_s->conf->ctrl_interface,
		sizeof(addr.sun_path));
	if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		perror("bind(PF_UNIX)");
		close(s);
		return -1;
	}

	wpa_s->ctrl_sock = s;
	eloop_register_read_sock(s, wpa_supplicant_ctrl_iface_receive, wpa_s,
				 NULL);

	return 0;
}


void wpa_supplicant_ctrl_iface_deinit(struct wpa_supplicant *wpa_s)
{
	if (wpa_s->ctrl_sock > -1) {
		eloop_unregister_read_sock(wpa_s->ctrl_sock);
		close(wpa_s->ctrl_sock);
		wpa_s->ctrl_sock = -1;
		unlink(wpa_s->conf->ctrl_interface);
	}
}
